package org.budo.graph.service.util;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.math.NumberUtils;
import org.budo.graph.druid.filter.stat.BudoStatFilter;
import org.budo.graph.metric.Metric;
import org.budo.graph.metric.MetricUtil;
import org.budo.support.fastjson.Fastjson;
import org.budo.support.lang.util.ListUtil;
import org.budo.support.lang.util.MapUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.SetUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.servlet.util.QueryStringUtil;
import org.budo.support.slf4j.Slf4j;
import org.slf4j.Logger;

import com.alibaba.druid.stat.DruidStatManagerFacade;
import com.alibaba.druid.stat.DruidStatService;
import com.alibaba.druid.support.http.stat.WebAppStatManager;
import com.alibaba.druid.support.spring.stat.SpringStatManager;
import com.alibaba.druid.util.MapComparator;

/**
 * @author limingwei
 * @date 2016年6月7日 下午2:50:01
 */
public class GraphServiceUtil {
    private static final Logger log = Slf4j.getLogger();

    private static final String DUBBO_BYTECODE = "com.alibaba.dubbo.common.bytecode.";

    private final static int DEFAULT_PAGE = 1;

    private final static int DEFAULT_PER_PAGE_COUNT = Integer.MAX_VALUE;

    private static final String DEFAULT_ORDER_TYPE = "asc";

    /**
     * 跳过不传输的属性
     */
    private static final String[] WEB_URI_SKIP_KEYS = { "Profiles", "ErrorCount", "LastAccessTime", "JdbcExecutePeak", "JdbcUpdatePeak", //
            "JdbcFetchRowPeak", "JdbcResultSetCloseCount", "JdbcResultSetOpenCount", "JdbcPoolConnectionCloseCount", "JdbcPoolConnectionOpenCount" };

    /**
     * 跳过不传输的属性
     */
    private static final String[] SQL_SKIP_KEYS_ALL = { "ReadStringLength", "BlobOpenCount", "InputStreamOpenCount", "DbType", "HASH", //
            "DataSource", "LastErrorClass", "BatchSizeTotal", "URL", "ExecuteAndResultSetHoldTime", //
            "LastErrorMessage", "FetchRowCountMax", "ClobOpenCount", "LastTime", "File", //
            "LastErrorStackTrace", "LastError", "Name", "MaxTimespanOccurTime", "BatchSizeMax", //
            "ReadBytesLength", "EffectedRowCountMax", "ResultSetHoldTime", "ReaderOpenCount", "LastErrorTime" };

    /**
     * 跳过不传输的属性
     */
    private static final String[] SQL_SKIP_KEYS_LT_10 = { "Histogram", "FetchRowCountHistogram", "EffectedRowCountHistogram", "ExecuteAndResultHoldTimeHistogram" };

    /**
     * Druid调用伪装为Web调用
     * 
     * @see #springMethodStatData(String)
     */
    public static String webUriListData(String url) {
        WebAppStatManager instance = WebAppStatManager.getInstance();

        if (null == instance) {
            return "WEB_APP_STAT_MANAGER_INSTANCE_IS_NULL";
        }

        String defaultOrderBy = "RequestTimeMillis";

        List<Map<String, Object>> uriStatDataMapList = instance.getURIStatData();

        Map<String, String> parameters = QueryStringUtil.queryStringToStringMap(url);
        List<Map<String, Object>> sortedUriStatDataMapList = comparatorOrderBy(uriStatDataMapList, parameters, defaultOrderBy);

        if (ListUtil.isNullOrEmpty(sortedUriStatDataMapList)) {
            return DruidStatService.returnJSONResult(DruidStatService.RESULT_CODE_SUCCESS, sortedUriStatDataMapList);
        }

        Set<String> budoGraphMethods = BudoGraphUtil.getBudoGraphMethods();

        for (Map<String, Object> map : sortedUriStatDataMapList) {
            MapUtil.remove(map, WEB_URI_SKIP_KEYS); // 移除要跳过的属性

            // 有 @BudoGraph
            String uri = map.get("URI") + "";
            if (SetUtil.contains(budoGraphMethods, uri)) {
                map.put("BudoGraph", "1");
            }

            String executeCount = map.get("RequestCount") + "";
            if (NumberUtils.isDigits(executeCount)) {
                int executeCountInt = NumberUtils.toInt(executeCount);
                if (executeCountInt < 10) { // 调用次数在 10 以下的，列表页不展示时间分布
                    map.remove("Histogram");
                }
            }

            map.put("SentinelQpsMax", GraphSentinelUtil.flowRuleCount(uri));
        }

        return DruidStatService.returnJSONResult(DruidStatService.RESULT_CODE_SUCCESS, sortedUriStatDataMapList);
    }

    /**
     * 减少结果输出
     */
    public static String sqlListData(String url) {
        DruidStatManagerFacade druidStatManager = DruidStatManagerFacade.getInstance();
        if (null == druidStatManager) {
            return "DRUID_STAT_MANAGER_FACADE_INSTANCE_IS_NULL";
        }

        Map<String, String> parameters = QueryStringUtil.queryStringToStringMap(url);

        // 前端要求筛选包含这个表的SQL
        String table = parameters.get("table");

        Integer dataSourceId = NumberUtils.toInt(parameters.get("dataSourceId"));

        List<Map<String, Object>> sqlStatDataList = druidStatManager.getSqlStatDataList(dataSourceId);
        List<Map<String, Object>> sortedSqlStatDataList = comparatorOrderBy(sqlStatDataList, parameters, "TotalTime");

        if (ListUtil.isNullOrEmpty(sortedSqlStatDataList)) {
            log.error("#128 sortedSqlStatDataList = " + sortedSqlStatDataList);
            return DruidStatService.returnJSONResult(DruidStatService.RESULT_CODE_SUCCESS, sortedSqlStatDataList);
        }

        for (Map<String, Object> map : sortedSqlStatDataList) {
            MapUtil.remove(map, SQL_SKIP_KEYS_ALL); // 移除不需要的数据

            String executeCount = map.get("ExecuteCount") + "";
            if (NumberUtils.isDigits(executeCount)) {
                int executeCountInt = NumberUtils.toInt(executeCount);
                if (executeCountInt < 10) { // 调用次数在 10 以下的，列表页不展示时间分布
                    MapUtil.remove(map, SQL_SKIP_KEYS_LT_10);
                }
            }

            // 去掉空格作为KEY
            String sql = (String) map.get("SQL");
            String sql_2 = _sql_2_(sql);
            map.put("SQL_2", sql_2);

            // 是否有监控
            if (BudoStatFilter.hasBudoGraph(sql)) {
                map.put("BudoGraph", "1");
            }
        }

        // 根据表名筛选 移除无关的行
        Iterator<Map<String, Object>> iterator = sortedSqlStatDataList.iterator();
        while (iterator.hasNext()) {
            Map<String, Object> map = iterator.next();
            String sql = (String) map.get("SQL");
            if (null != table //
                    && !table.trim().isEmpty() // 要求筛选
                    && !"undefined".equalsIgnoreCase(table.trim()) //
                    && !sql.contains(table)) { // 不符合筛选条件
                iterator.remove(); // 移除
            }
        }

        // 组装返回
        return DruidStatService.returnJSONResult(DruidStatService.RESULT_CODE_SUCCESS, sortedSqlStatDataList);
    }

    /**
     * 处理掉SQL中的特殊字符
     */
    public static String _sql_2_(String sql) {
        return sql.replace(" ", "") //
                .replace("`", "") //
                .replace("\t", "") //
                .replace("\n", "");
    }

    public static String websessionStatData(String url) {
        Map<String, String> parameters = QueryStringUtil.queryStringToStringMap(url);
        List<Map<String, Object>> dataMapList = WebAppStatManager.getInstance().getSessionStatData();
        List<Map<String, Object>> orderdDataMapList = comparatorOrderBy(dataMapList, parameters, "RequestTimeMillisTotal");

        for (Map<String, Object> map : orderdDataMapList) {
            String sessionId = (String) map.get("SESSIONID");
            map.put("hasSessionRequest", SessionRequestUtil.hasSessionRequest(sessionId));
        }

        return DruidStatService.returnJSONResult(DruidStatService.RESULT_CODE_SUCCESS, orderdDataMapList);
    }

    /**
     * @see #webUriListData(String)
     */
    public static String springMethodStatData(String url) {
        Map<String, String> parameters = QueryStringUtil.queryStringToStringMap(url);

        // 前端要求筛选包含这个方法
        String targetMethod = parameters.get("method");

        List<Map<String, Object>> methodStatData = SpringStatManager.getInstance().getMethodStatData();

        // 排序
        String defaultOrderBy = "ExecuteTimeMillis";
        methodStatData = comparatorOrderBy(methodStatData, parameters, defaultOrderBy);

        // 将数据处理一下
        methodStatData = modifySpringMethodStatData(methodStatData);

        // 根据方法名筛选 移除无关的行
        methodStatData = removeNotMatchMethod(methodStatData, targetMethod);

        return DruidStatService.returnJSONResult(DruidStatService.RESULT_CODE_SUCCESS, methodStatData);
    }

    private static List<Map<String, Object>> removeNotMatchMethod(List<Map<String, Object>> methodStatData, String targetMethod) {
        Iterator<Map<String, Object>> iterator = methodStatData.iterator();

        while (iterator.hasNext()) {
            Map<String, Object> map = iterator.next();
            String thisMethod = (String) map.get("Method");
            String interfaceMethod = (String) map.get("InterfaceMethod");

            Boolean methodMatch = methodMatch(targetMethod, thisMethod, interfaceMethod);
            if (!methodMatch) { // 不符合筛选条件
                iterator.remove(); // 移除
            }
        }

        return methodStatData;
    }

    private static Boolean methodMatch(String targetMethod, String thisMethod, String interfaceMethod) {
        if (null == targetMethod //
                || targetMethod.trim().isEmpty() //
                || "undefined".equalsIgnoreCase(targetMethod.trim())) {
            return true; // 不限制 全都 match
        }

        if (StringUtil.contains(thisMethod, targetMethod) //
                || StringUtil.contains(interfaceMethod, targetMethod)) {
            return true; // 直接匹配 match
        }

        String targetNameArgs = ReflectUtil.methodNameAndArgs(targetMethod);
        String thisNameArgs = ReflectUtil.methodNameAndArgs(targetMethod);
        String interfaceNameArgs = ReflectUtil.methodNameAndArgs(targetMethod);

        // 方法名称和参数不匹配
        if (!StringUtil.equalsIgnoreCase(targetNameArgs, thisNameArgs) //
                && !StringUtil.equalsIgnoreCase(targetNameArgs, interfaceNameArgs)) {
            return false;
        }

        // 方法名和参数已匹配，看方法声明的类是否匹配
        String targetDeclaring = ReflectUtil.methodDeclaringClassName(targetMethod);
        String thisDeclaring = ReflectUtil.methodDeclaringClassName(targetMethod);
        String interfaceDeclaring = ReflectUtil.methodDeclaringClassName(targetMethod);

        // 有继承关系 类型 匹配上了
        if (ReflectUtil.isAssignableFrom(targetDeclaring, thisDeclaring) //
                || ReflectUtil.isAssignableFrom(targetDeclaring, interfaceDeclaring) //
                || ReflectUtil.isAssignableFrom(thisDeclaring, targetDeclaring) //
                || ReflectUtil.isAssignableFrom(interfaceDeclaring, targetDeclaring)) {
            return true;
        }

        return false;
    }

    /**
     * 将数据处理一下
     */
    private static List<Map<String, Object>> modifySpringMethodStatData(List<Map<String, Object>> methodStatData) {
        if (ListUtil.isNullOrEmpty(methodStatData)) {
            return methodStatData;
        }

        Set<String> springGraphMethods = BudoGraphUtil.getSpringGraphMethods();

        for (Map<String, Object> map : methodStatData) {
            String methodFullName = map.get("Method") + "";
            if (StringUtil.startsWith(methodFullName, DUBBO_BYTECODE)) {
                map.put("InterfaceMethod", replaceInterfaceTypeName(methodFullName));
            }

            // 方法上有 @SpringGraph
            String method = map.get("Method") + "";
            if (SetUtil.contains(springGraphMethods, method)) {
                map.put("BudoGraph", "1");
            }
        }

        return methodStatData;
    }

    public static String webUriStatData(String url) {
        String uri = StringUtil.substring(url, "weburi-", ".json", true);
        Map<String, Object> uriStatData = WebAppStatManager.getInstance().getURIStatData(uri);
        uriStatData = modifyWebUriStatData(uriStatData);
        return DruidStatService.returnJSONResult(DruidStatService.RESULT_CODE_SUCCESS, uriStatData);
    }

    private static Map<String, Object> modifyWebUriStatData(Map<String, Object> uriStatData) {
        if (MapUtil.isNullOrEmpty(uriStatData)) {
            return uriStatData;
        }

        List<Map<String, Object>> profiles = (List<Map<String, Object>>) uriStatData.get("Profiles");
        if (ListUtil.isNullOrEmpty(profiles)) {
            return uriStatData;
        }

        for (Map<String, Object> profile : profiles) {
            String name = profile.get("Name") + "";
            if (StringUtil.startsWith(name, DUBBO_BYTECODE)) {
                profile.put("Name2", replaceInterfaceTypeName(name));
            }

            String parent = profile.get("Parent") + "";
            if (StringUtil.startsWith(parent, DUBBO_BYTECODE)) {
                profile.put("Parent2", replaceInterfaceTypeName(parent));
            }
        }

        return uriStatData;
    }

    private static String replaceInterfaceTypeName(String methodFullName) {
        int afterMethodName = methodFullName.indexOf('(');

        String typeAndMethod = methodFullName.substring(0, afterMethodName);
        int afterTypeName = typeAndMethod.lastIndexOf('.');

        String methodTypeName = methodFullName.substring(0, afterTypeName);
        String methodSimpleName = methodFullName.substring(afterTypeName);

        if (!ReflectUtil.hasClass(methodTypeName)) {
            log.error("#196 replaceInterfaceTypeName error, methodFullName=" + methodFullName + ", methodTypeName=" + methodTypeName + ", methodSimpleName=" + methodSimpleName);
            return methodFullName;
        }

        String methodInterfaceName = interfaceTypeName(methodTypeName);
        return "[D] " + methodInterfaceName + methodSimpleName;
    }

    private static String interfaceTypeName(String methodTypeName) {
        Class methodType = ReflectUtil.classForName(methodTypeName);
        Class[] interfaces = methodType.getInterfaces();
        if (null == interfaces || interfaces.length < 1) {
            return "NoInterface";
        }

        for (Class interfaceType : interfaces) {
            String interfaceTypeName = interfaceType.getName();
            if (!StringUtil.startsWith(interfaceTypeName, "com.alibaba.dubbo.")) {
                return interfaceTypeName;
            }
        }

        return "DubboInterface";
    }

    /**
     * 结果排序，并分页
     */
    public static List<Map<String, Object>> comparatorOrderBy(List<Map<String, Object>> dataList, Map<String, String> parameters, String defaultOrderBy) {
        if (dataList == null || dataList.isEmpty()) {
            return dataList;
        }

        String orderBy, orderType = null;
        Integer page = DEFAULT_PAGE;
        Integer perPageCount = DEFAULT_PER_PAGE_COUNT;

        if (null == parameters) {
            orderBy = DEFAULT_ORDER_TYPE;
            orderType = DEFAULT_ORDER_TYPE;
            page = DEFAULT_PAGE;
            perPageCount = DEFAULT_PER_PAGE_COUNT;
        } else {
            orderBy = parameters.get("orderBy");
            orderType = parameters.get("orderType");

            String pageParam = parameters.get("page");
            page = NumberUtils.toInt(pageParam, DEFAULT_PAGE);

            String pageCountParam = parameters.get("perPageCount");
            perPageCount = NumberUtils.toInt(pageCountParam, DEFAULT_PER_PAGE_COUNT);
        }

        // others, such as order
        orderBy = (null == orderBy || orderBy.trim().isEmpty()) ? defaultOrderBy : orderBy;
        orderType = (null == orderType || orderType.trim().isEmpty()) ? DEFAULT_ORDER_TYPE : orderType;

        if (!"desc".equals(orderType)) {
            orderType = DEFAULT_ORDER_TYPE;
        }

        // orderby the statData array
        if (orderBy != null && orderBy.trim().length() != 0) {
            Collections.sort(dataList, new MapComparator<String, Object>(orderBy, DEFAULT_ORDER_TYPE.equals(orderType)));
        }

        // page
        int fromIndex = (page - 1) * perPageCount;
        int toIndex = page * perPageCount;
        if (toIndex > dataList.size()) {
            toIndex = dataList.size();
        }

        return dataList.subList(fromIndex, toIndex);
    }

    public static String metricList(String url) {
        Collection<Metric> metrics = MetricUtil.metricMap().values();

        Map<String, String> parameters = QueryStringUtil.queryStringToStringMap(url);
        String metricKey = parameters.get("metricKey");

        if (null == metricKey || metricKey.trim().isEmpty() //
                || "null".equalsIgnoreCase(metricKey.trim()) //
                || "undefined".equalsIgnoreCase(metricKey.trim())) {
            return Fastjson.toJSONString(metrics);
        }

        // 筛选
        Iterator<Metric> iterator = metrics.iterator();
        while (iterator.hasNext()) {
            Metric next = iterator.next();
            String thisKey = next.getKey().substring(0, next.getKey().lastIndexOf('.'));
            String targetKey = metricKey.substring(0, metricKey.lastIndexOf('.'));

            if (!thisKey.equalsIgnoreCase(targetKey)) {
                iterator.remove();
            }
        }

        return Fastjson.toJSONString(metrics);
    }
}